package com.eyas.im.common.datasource;

import com.eyas.im.ApplicationConstant;
import com.eyas.im.common.datasource.impl.DefaultRowMapper;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public enum DataSourceUtils implements Closeable {
    INSTANCE;
    public final static Logger logger = LoggerFactory.getLogger(DataSourceUtils.class);
    private final HikariDataSource dataSource;

    private DataSourceUtils() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.database.driver-class"));
        hikariConfig.setJdbcUrl(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.database.url"));
        hikariConfig.setUsername(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.database.username"));
        hikariConfig.setPassword(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.database.password"));
        hikariConfig.setMaximumPoolSize(Integer.parseInt(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.datasource.max-size")));
        hikariConfig.setMinimumIdle(Integer.parseInt(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.datasource.min-size")));
        hikariConfig.setPoolName("eyas-im-datasrouce");
        hikariConfig.setConnectionInitSql(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.datasource.connection-init-sql"));
        this.dataSource = new HikariDataSource(hikariConfig);
    }

    /**
     * 数据库建立连接后，执行初始化脚本工作
     *
     * @throws Exception
     */
    public void executeInitalSql() throws Exception {
        boolean exitOnerror = Boolean.parseBoolean(ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.database.initial-error-exit"));
        String initail_script_path = ApplicationConstant.APP_CONFIG_PROPERTIES.getString("com.eyas.im.database.initial-script-path");
        URL initialScriptUrl = Thread.currentThread().getContextClassLoader().getResource(initail_script_path);
        assert initialScriptUrl != null;
        Path initalScriptPath = Paths.get(initialScriptUrl.toURI());
        StringBuilder sql = new StringBuilder();
        try (InputStream inputStream = Files.newInputStream(initalScriptPath, StandardOpenOption.READ);
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
            String lineStr = null;
            boolean sqlEnd = false;
            while ((lineStr = bufferedReader.readLine()) != null) {
                if (lineStr.contains(";")) {
                    lineStr = lineStr.substring(0, lineStr.length() - 1);
                    sqlEnd = true;
                }
                sql.append(lineStr);
                if (sqlEnd) {
                    sqlEnd = false;
                    try (Connection connection = this.getConnection();
                         Statement statement = connection.createStatement()) {
                        if (statement.execute(sql.toString())) {
                            statement.getResultSet().close();
                        } else {
                            statement.getUpdateCount();
                        }
                    } catch (SQLException e) {
                        logger.warn("初始化数据库失败:{}", e.getLocalizedMessage());
                        if (exitOnerror)
                            throw e;
                    } finally {
                        sql = new StringBuilder();
                    }

                }
            }
        }

        logger.info("数据库初始化结束");
    }

    @Override
    public void close() throws IOException {
        logger.debug("关闭数据源");
        this.dataSource.close();
    }

    /**
     * 获取connection对象，需要释放回datasource pool时，直接调用connection.close即可
     *
     * @return
     * @throws SQLException
     */
    public Connection getConnection() throws SQLException {
        return this.dataSource.getConnection();
    }

    /**
     *
     * @param sql
     * @param clazz
     * @param args
     * @param <E>
     * @return 查询结果为空，返回NULL
     * @throws Exception  反射和SQL异常
     */
    public <E> List<E> query(String sql, Class<E> clazz, Object... args) throws Exception {
        List<E> result = null;
        try(Connection connection = this.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            int paramIndex = 0;
            for(Object arg:args) {
                paramIndex++;
                //H2和oracle都会根据arg的类型自动调用相应的setXxx方法，mysql还没确认
                preparedStatement.setObject(paramIndex,arg);
            }
            try(ResultSet resultSet = preparedStatement.executeQuery()) {
                if(resultSet.next()) {
                    RowMapper rowMapper = new DefaultRowMapper(resultSet.getMetaData());
                    result = new ArrayList<>();
                    do {
                        result.add(rowMapper.rawDataToObject(resultSet,clazz));
                    }while(resultSet.next());
                }
            }
        }

        return result;
    }

    /**
     * 执行增删改操作
     * @param sql
     * @param args
     * @return
     * @throws SQLException
     */
    public int execute(String sql,Object... args) throws SQLException {
        try(Connection connection = this.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            int paramIndex = 0;
            for(Object arg:args) {
                paramIndex++;
                //H2和oracle都会根据arg的类型自动调用相应的setXxx方法，mysql还没确认
                preparedStatement.setObject(paramIndex,arg);
            }
            return preparedStatement.executeUpdate();
        }
    }
}
